//  Copyright 2011 Charlie Garrett
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
//  logic.cpp
//

#include <fstream>

#include "logic.h"
#include "CodeGenerator.h"
#include "Instruction.h"
#include "Interpreter.h"
#include "Parser.h"
#include "Term.h"
#include "TermVisitor.h"

// FIXES:
// First, you have to generate the code.  Then you have to initialize the machine
// and run the code.  At least register X1 must match for the outermost program
// and query term.  Not sure about the others because they're covered by unify_*
// instructions before get_structure instructions.

namespace logic {
using namespace std;
    
    Structure *CreateStructure(const string &name, Term *arg1) {
        vector<Term*> args;
        args.push_back(arg1);
        return new Structure(name, args);
    }
    
    Structure *CreateStructure(const string &name, Term *arg1, Term *arg2) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        return new Structure(name, args);
    }
    
    Structure *CreateStructure(const string &name, Term *arg1, Term *arg2, Term *arg3) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        return new Structure(name, args);
    }
    
    Structure *CreateStructure(const string &name, Term *arg1, Term *arg2, Term *arg3, Term *arg4) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        args.push_back(arg4);
        return new Structure(name, args);
    }

    Structure *CreateStructure(const string &name, Term *arg1, Term *arg2, Term *arg3, Term *arg4,
                               Term *arg5) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        args.push_back(arg4);
        args.push_back(arg5);
        return new Structure(name, args);
    }

    Predicate *CreatePredicate(const string &name, Term *arg1) {
        vector<Term*> args;
        args.push_back(arg1);
        return new Predicate(name, args);
    }
    
    Predicate *CreatePredicate(const string &name, Term *arg1, Term *arg2) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        return new Predicate(name, args);
    }
    
    Predicate *CreatePredicate(const string &name, Term *arg1, Term *arg2, Term *arg3) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        return new Predicate(name, args);
    }
    
    Predicate *CreatePredicate(const string &name, Term *arg1, Term *arg2, Term *arg3, Term *arg4) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        args.push_back(arg4);
        return new Predicate(name, args);
    }
    
    Predicate *CreatePredicate(const string &name, Term *arg1, Term *arg2, Term *arg3, Term *arg4,
                               Term *arg5) {
        vector<Term*> args;
        args.push_back(arg1);
        args.push_back(arg2);
        args.push_back(arg3);
        args.push_back(arg4);
        args.push_back(arg5);
        return new Predicate(name, args);
    }
    
    Clause *CreateClause(Predicate *head) {
        vector<Predicate*> goals;
        return new Clause(head, goals);
    }

    Clause *CreateClause(Predicate *head, Predicate *body1) {
        vector<Predicate*> goals;
        goals.push_back(body1);
        return new Clause(head, goals);
    }

    Clause *CreateClause(Predicate *head, Predicate *body1, Predicate *body2) {
        vector<Predicate*> goals;
        goals.push_back(body1);
        goals.push_back(body2);
        return new Clause(head, goals);
    }

    Clause *CreateClause(Predicate *head, Predicate *body1, Predicate *body2, Predicate *body3) {
        vector<Predicate*> goals;
        goals.push_back(body1);
        goals.push_back(body2);
        goals.push_back(body3);
        return new Clause(head, goals);
    }
    
    Clause *CreateClause(Predicate *head, Predicate *body1, Predicate *body2, Predicate *body3,
                         Predicate *body4) {
        vector<Predicate*> goals;
        goals.push_back(body1);
        goals.push_back(body2);
        goals.push_back(body3);
        goals.push_back(body4);
        return new Clause(head, goals);
    }
        
    Clause *CreateClause(Predicate *head, Predicate *body1, Predicate *body2, Predicate *body3,
                         Predicate *body4, Predicate *body5) {
        vector<Predicate*> goals;
        goals.push_back(body1);
        goals.push_back(body2);
        goals.push_back(body3);
        goals.push_back(body4);
        goals.push_back(body5);
        return new Clause(head, goals);
    }
    
    Rule *CreateRule(Clause *clause1) {
        vector<Clause*> clauses;
        clauses.push_back(clause1);
        return new Rule(clauses);
    }

    Rule *CreateRule(Clause *clause1, Clause *clause2) {
        vector<Clause*> clauses;
        clauses.push_back(clause1);
        clauses.push_back(clause2);
        return new Rule(clauses);
    }
    
    Rule *CreateRule(Clause *clause1, Clause *clause2, Clause *clause3) {
        vector<Clause*> clauses;
        clauses.push_back(clause1);
        clauses.push_back(clause2);
        clauses.push_back(clause3);
        return new Rule(clauses);
    }
    
    Rule *CreateRule(Clause *clause1, Clause *clause2, Clause *clause3, Clause *clause4) {
        vector<Clause*> clauses;
        clauses.push_back(clause1);
        clauses.push_back(clause2);
        clauses.push_back(clause3);
        clauses.push_back(clause4);
        return new Rule(clauses);
    }
    
    Rule *CreateRule(Clause *clause1, Clause *clause2, Clause *clause3, Clause *clause4, Clause *clause5) {
        vector<Clause*> clauses;
        clauses.push_back(clause1);
        clauses.push_back(clause2);
        clauses.push_back(clause3);
        clauses.push_back(clause4);
        clauses.push_back(clause5);
        return new Rule(clauses);
    }
    
    // p(Z, h(Z, W), f(W))
    Predicate *CreateQuery1() {
        Variable* Z = new Variable("Z");
        Variable* W = new Variable("W");
        
        Structure *h = CreateStructure("h", Z, W);
        Structure *f = CreateStructure("f", W);

        Predicate *p = CreatePredicate("p", Z, h, f);
        cout << p->ToString() << endl;
        
        return p;
    }
    
    // do(parse(s(np, vp), cons(birds, cons(fly, nil)), nil))
    Predicate *CreateQuery2() {
        Constant *np = new Constant("np");
        Constant *vp = new Constant("vp");
        Constant *birds = new Constant("birds");
        Constant *fly = new Constant("fly");
        Constant *nil = new Constant("nil");
        
        Structure *s = CreateStructure("s", np, vp);
        Structure *cons1 = CreateStructure("cons", fly, nil);
        Structure *cons2 = CreateStructure("cons", birds, cons1);        
        Structure *parse = CreateStructure("parse", s, cons2, nil);        

        Predicate *do_pred = CreatePredicate("do", parse);
        cout << do_pred->ToString() << endl;
        
        return do_pred;
    }

    Predicate *CreateProgram1() {
        Constant *a = new Constant("a");

        Variable *X = new Variable("X");
        Variable *Y = new Variable("Y");
        
        Structure *f_X = CreateStructure("f", X);        
        Structure *f_a = CreateStructure("f", a);
        Structure *h = CreateStructure("h", Y, f_a);
        
        Predicate *p = CreatePredicate("p", f_X, h, Y);
        cout << p->ToString() << endl;
        
        return p;
    }
    
    Predicate *CreateProgram2() {
        Variable *U = new Variable("U");
        Variable *V = new Variable("V");
        Variable *X = new Variable("X");
        Variable *DU = new Variable("DU");
        Variable *DV = new Variable("DV");
        
        Structure *m1 = CreateStructure("*", U, V);
        Structure *m2 = CreateStructure("*", DU, V);
        Structure *m3 = CreateStructure("*", U, DV);
        Structure *plus = CreateStructure("+", m2, m3);
        
        Predicate *p = CreatePredicate("d", m1, X, plus);
        cout << p->ToString() << endl;
        
        return p;
    }
    
    Clause *CreateClause1() {
        Variable *X = new Variable("X");
        Variable *Y = new Variable("Y");
        Variable *Z = new Variable("Z");
        
        Predicate *head = CreatePredicate("p", X, Y);
        Predicate *body1 = CreatePredicate("q", X, Z);
        Predicate *body2 = CreatePredicate("r", Z, Y);
        
        Clause *clause = CreateClause(head, body1, body2);
        cout << clause->ToString() << endl;
        
        return clause;        
    }
    
    Rule *CreateRule1() {
        Variable *X = new Variable("X");
        Variable *Y = new Variable("Y");
        Variable *Z = new Variable("Z");

        Predicate *head = CreatePredicate("p", X, Y);
        Predicate *body1 = CreatePredicate("q", X, Z);
        Predicate *body2 = CreatePredicate("r", Z, Y);
        
        Clause *clause = CreateClause(head, body1, body2);
        
        Rule *rule = CreateRule(clause);
        cout << rule->ToString() << endl;
        
        return rule;
    }
    
    Rule *CreateRule2() {
        Constant *a = new Constant("a");
        Constant *b = new Constant("b");
        
        Predicate *head = CreatePredicate("q", a, b);
        
        Clause *clause = CreateClause(head);
        Rule *rule = CreateRule(clause);
        cout << rule->ToString() << endl;
        
        return rule;
    }

    Rule *CreateRule3() {
        Constant *b = new Constant("b");
        Constant *c = new Constant("c");
        
        Predicate *head = CreatePredicate("r", b, c);
        
        Clause *clause = CreateClause(head);
        Rule *rule = CreateRule(clause);
        cout << rule->ToString() << endl;
        
        return rule;
    }
    
    Predicate *CreateQuery3() {
        Variable *U = new Variable("U");
        Variable *V = new Variable("V");
        
        Predicate *query = CreatePredicate("p", U, V);
        cout << query->ToString() << endl;
        
        return query;
    }
    
    Rule *CreateRule4() {
        Constant *a = new Constant("a");
        
        Variable *X = new Variable("X");
        Variable *Y = new Variable("Y");
        
        Structure *f_X = CreateStructure("f", X);        
        Structure *f_a = CreateStructure("f", a);
        Structure *h = CreateStructure("h", Y, f_a);
        
        Predicate *head = CreatePredicate("p", f_X, h, Y);
        Rule *rule = CreateRule(CreateClause(head));
        cout << rule->ToString() << endl;
        
        return rule;
    }
    
    Predicate *CreateQuery4() {
        Variable *W = new Variable("W");
        Variable *Z = new Variable("Z");
        
        Structure *f = CreateStructure("f", W);        
        Structure *h = CreateStructure("h", Z, W);
        
        Predicate *query = CreatePredicate("p", Z, h, f);
        cout << query->ToString() << endl;
        
        return query;
    }
    
    void LOneTest7() {
        cout << "Test #7" << endl;
        Interpreter *interpreter = new Interpreter();
        interpreter->InitState();
        CodeGenerator *codegen = new CodeGenerator(interpreter);
        codegen->InitState();
        
        Clause *c = CreateClause1();
        c->Translate(codegen, 1);
        
        interpreter->PrintCode();
        interpreter->PrintState();
    }
    
    void LOneTest8() {
        // Exercise 3.1 from Ait-Kaci
        cout << "Test #8" << endl;
        
        Rule *p = CreateRule1();
        Rule *q = CreateRule2();
        Rule *r = CreateRule3();
        
        vector<Rule*> rules;
        rules.push_back(p);
        rules.push_back(q);
        rules.push_back(r);
        Program *program = new Program(rules);

        Predicate *query = CreateQuery3();
        program->RunQuery(query);
    }

    void LOneTest9() {
        // Exercise 2.3 from Ait-Kaci
        cout << "Test #9" << endl;
        
        Rule *r = CreateRule4();
        vector<Rule*> rules;
        rules.push_back(r);
        Program *program = new Program(rules);
        
        Predicate *query = CreateQuery4();
        program->RunQuery(query);
    }
    
    void TestInterpreter() {
//        LOneTest7();
//        LOneTest8();
        LOneTest9();
    }
    
    void TestInput1() {
        const char *filename = "./TestInput1.pl";
        ifstream filestream(filename);
        
        if (!filestream) {
            cerr << "Can't open file named " << filename << endl;
            return;
        }
        
        cout << "Parse program from " << filename << endl;
        Parser parser(filestream);
        Program *program = parser.ParseProgram();
        if (program != NULL) {
            cout << "Parsed program:" << endl << program->ToString();
        } else {
            cout << "Failed to parse program!" << endl;
        }
    }

    void TestInput2() {
        const char *filename = "./TestInput2.pl";
        ifstream filestream(filename);
        
        if (!filestream) {
            cerr << "Can't open file named " << filename << endl;
            return;
        }
        
        cout << "Parse program from " << filename << endl;
        Parser parser(filestream);
        Program *program = parser.ParseProgram();
        if (program != NULL) {
            cout << "Parsed program:" << endl << program->ToString();
            program->RunQueries();
        } else {
            cout << "Failed to parse program!" << endl;
        }
    }
    
    void TestInput() {
        TestInput1();
        TestInput2();
    }
    
    void InterpretProgram(const char *filename, bool trace, bool interactive) {
        ifstream filestream(filename);
        
        if (!filestream) {
            cerr << "Can't open file named " << filename << endl;
            return;
        }
        
        cout << "Parse program from " << filename << endl;
        Parser parser(filestream);
        Program *program = parser.ParseProgram();
        if (program == NULL) {
            cout << "Failed to parse program!" << endl;
            return;
        }
            
        if (interactive) {
            StartShell(program, trace);
        } else {
            cout << "Parsed program:" << endl << program->ToString();
            program->RunQueries(trace);
        }        
    }
    
    bool ReadCommand(string *command) {
        command->clear();
        command->append("?- ");
        
        char part;
        cin >> noskipws;
        while (cin >> part) {
            if (part == '\n') {
                return true;
            }
            command->push_back(part);
        }
        return false;
    }
    
    void StartShell(Program *program, bool trace) {
        // An interpreter is too big to allocate on the stack.
        // TODO: Reduce memory footprint of interpreter.
        Interpreter *interpreter = new Interpreter(trace);
        interpreter->InitState();
        CodeGenerator codegen(interpreter);
        codegen.InitState();
        
        program->Translate(&codegen);
        
        if (trace) {
            interpreter->PrintCode();
        }
        
        const string prompt("?- ");
        cout << "Starting interactive shell" << endl;
        
        unsigned num_queries = 0;
        while (true) {
            Predicate *query = NULL;
            while (query == NULL) {
                cout << prompt;
                string command;
                getline(cin, command);
                
                if (cin.eof()) {
                    return;
                }
            
                string qstring("?- ");
                qstring.append(command);

                // cout << "Echoing command \"" << qstring << "\"" << endl;
                istringstream command_stream(qstring);
                Parser parser(command_stream);
                query = parser.ParseQuery();
                if (query == NULL) {
                    cout << "Failed to parse query: " << qstring << endl;
                }
            }
            
            interpreter->InitState(false);
            // Use a dummy entry point for the query.
            Instruction::Label query_label =
                codegen.LabelInstruction(Functor("QEP", 0xffffffff), num_queries++);
            query->TranslateQuery(&codegen, true);
            if (codegen.ResolveLabels()) {
                
                // interpreter->PrintCode();
                size_t offset = 0;
                if (codegen.LabelOffset(query_label, &offset)) {
                    if (interpreter->RunFrom(offset)) {
                        interpreter->PrintAnswer();
                        while (interpreter->MoreAnswers()) {
                            cout << " ";
                            string more_answers;
                            getline(cin, more_answers);
                            
                            if (cin.eof()) {
                                return;
                            }
                            
                            // cout << "Echoing char '" << more_answers << "'" << endl;
                            if (more_answers == ";") {
                                if (!interpreter->FailAndContinue()) {
                                    break;
                                }
                                interpreter->PrintAnswer();
                            } else {
                                break;
                            }
                        }
                    }
                } else {
                    cout << "Undefined predicate " << query_label.first.ToString() << endl;
                }
            } else {
                cout << "Unable to resolve all labels" << endl;
            }
            
            codegen.ClearArgRegisters();
            codegen.ClearTempRegisters();
            codegen.RemoveInstructions(query_label);
            
            cout << endl;
        }
    }

}  // namespace logic
